package studio.baka.satoripixeldungeon.actors.buffs;

import studio.baka.satoripixeldungeon.Assets;
import studio.baka.satoripixeldungeon.Badges;
import studio.baka.satoripixeldungeon.Dungeon;
import studio.baka.satoripixeldungeon.actors.Actor;
import studio.baka.satoripixeldungeon.actors.Char;
import studio.baka.satoripixeldungeon.actors.hero.Hero;
import studio.baka.satoripixeldungeon.effects.Pushing;
import studio.baka.satoripixeldungeon.items.BrokenSeal;
import studio.baka.satoripixeldungeon.items.Item;
import studio.baka.satoripixeldungeon.messages.Messages;
import studio.baka.satoripixeldungeon.scenes.CellSelector;
import studio.baka.satoripixeldungeon.scenes.GameScene;
import studio.baka.satoripixeldungeon.sprites.ItemSprite;
import studio.baka.satoripixeldungeon.sprites.ItemSpriteSheet;
import studio.baka.satoripixeldungeon.ui.ActionIndicator;
import studio.baka.satoripixeldungeon.ui.AttackIndicator;
import studio.baka.satoripixeldungeon.ui.BuffIndicator;
import studio.baka.satoripixeldungeon.utils.GLog;
import com.watabou.noosa.Image;
import com.watabou.noosa.audio.Sample;
import com.watabou.utils.Bundle;
import com.watabou.utils.PathFinder;
import com.watabou.utils.Random;

public class Combo extends Buff implements ActionIndicator.Action {

    private int count = 0;
    private float comboTime = 0f;
    private int misses = 0;

    @Override
    public int icon() {
        return BuffIndicator.COMBO;
    }

    @Override
    public void tintIcon(Image icon) {
        if (comboTime >= 3f) {
            icon.resetColor();
        } else {
            icon.tint(0xb3b3b3, 0.5f + 0.5f * (3f + 1 - comboTime) / 3f);
        }
    }

    @Override
    public String toString() {
        return Messages.get(this, "name");
    }

    public void hit(Char enemy) {

        count++;
        comboTime = 4f;
        misses = 0;
        BuffIndicator.refreshHero();

        if (count >= 2) {

            ActionIndicator.setAction(this);
            Badges.validateMasteryCombo(count);

            GLog.p(Messages.get(this, "combo", count));

        }

    }

    public void miss(Char enemy) {
        misses++;
        comboTime = 4f;
        if (misses >= 5) {
            detach();
        }
    }

    @Override
    public void detach() {
        super.detach();
        ActionIndicator.clearAction(this);
    }

    @Override
    public boolean act() {
        comboTime -= TICK;
        spend(TICK);
        BuffIndicator.refreshHero();
        if (comboTime <= 0) {
            detach();
        }
        return true;
    }

    @Override
    public String desc() {
        String desc = Messages.get(this, "desc");

        if (count >= 10) desc += "\n\n" + Messages.get(this, "fury_desc");
        else if (count >= 8) desc += "\n\n" + Messages.get(this, "crush_desc");
        else if (count >= 6) desc += "\n\n" + Messages.get(this, "slam_desc");
        else if (count >= 4) desc += "\n\n" + Messages.get(this, "cleave_desc");
        else if (count >= 2) desc += "\n\n" + Messages.get(this, "clobber_desc");

        return desc;
    }

    private static final String COUNT = "count";
    private static final String TIME = "combotime";
    private static final String MISSES = "misses";

    @Override
    public void storeInBundle(Bundle bundle) {
        super.storeInBundle(bundle);
        bundle.put(COUNT, count);
        bundle.put(TIME, comboTime);
        bundle.put(MISSES, misses);
    }

    @Override
    public void restoreFromBundle(Bundle bundle) {
        super.restoreFromBundle(bundle);
        count = bundle.getInt(COUNT);
        if (count >= 2) ActionIndicator.setAction(this);
        comboTime = bundle.getFloat(TIME);
        misses = bundle.getInt(MISSES);
    }

    @Override
    public Image getIcon() {
        Image icon;
        if (((Hero) target).belongings.weapon != null) {
            icon = new ItemSprite(((Hero) target).belongings.weapon.image, null);
        } else {
            icon = new ItemSprite(new Item() {
                {
                    image = ItemSpriteSheet.WEAPON_HOLDER;
                }
            });
        }

        if (count >= 10) icon.tint(0xFFFF0000);
        else if (count >= 8) icon.tint(0xFFFFCC00);
        else if (count >= 6) icon.tint(0xFFFFFF00);
        else if (count >= 4) icon.tint(0xFFCCFF00);
        else icon.tint(0xFF00FF00);

        return icon;
    }

    @Override
    public void doAction() {
        GameScene.selectCell(finisher);
    }

    private enum finisherType {
        CLOBBER, CLEAVE, SLAM, CRUSH, FURY
    }

    private final CellSelector.Listener finisher = new CellSelector.Listener() {

        private finisherType type;

        @Override
        public void onSelect(Integer cell) {
            if (cell == null) return;
            final Char enemy = Actor.findChar(cell);
            if (enemy == null
                    || !Dungeon.level.heroFOV[cell]
                    || !((Hero) target).canAttack(enemy)
                    || target.isCharmedBy(enemy)) {
                GLog.w(Messages.get(Combo.class, "bad_target"));
            } else {
                target.sprite.attack(cell, () -> {
                    if (count >= 10) type = finisherType.FURY;
                    else if (count >= 8) type = finisherType.CRUSH;
                    else if (count >= 6) type = finisherType.SLAM;
                    else if (count >= 4) type = finisherType.CLEAVE;
                    else type = finisherType.CLOBBER;
                    doAttack(enemy);
                });
            }
        }

        private void doAttack(final Char enemy) {

            AttackIndicator.target(enemy);

            int dmg = target.damageRoll();

            //variance in damage dealt
            switch (type) {
                case CLOBBER:
                case FURY:
                    dmg = Math.round(dmg * 0.6f);
                    break;
                case CLEAVE:
                    dmg = Math.round(dmg * 1.5f);
                    break;
                case SLAM:
                    dmg += target.drRoll();
                    break;
                case CRUSH:
                    //rolls 4 times, takes the highest roll
                    for (int i = 1; i < 4; i++) {
                        int dmgReroll = target.damageRoll();
                        if (dmgReroll > dmg) dmg = dmgReroll;
                    }
                    dmg = Math.round(dmg * 2.5f);
                    break;
            }

            dmg = enemy.defenseProcess(target, dmg);
            dmg -= enemy.drRoll();
            dmg = target.attackProc(enemy, dmg);
            enemy.damage(dmg, this);

            //special effects
            switch (type) {
                case CLOBBER:
                    if (enemy.isAlive()) {
                        if (!enemy.properties().contains(Char.Property.IMMOVABLE)) {
                            for (int i = 0; i < PathFinder.NEIGHBOURS8.length; i++) {
                                int ofs = PathFinder.NEIGHBOURS8[i];
                                if (enemy.pos - target.pos == ofs) {
                                    int newPos = enemy.pos + ofs;
                                    if ((Dungeon.level.passable[newPos] || Dungeon.level.avoid[newPos])
                                            && Actor.findChar(newPos) == null) {

                                        Actor.addDelayed(new Pushing(enemy, enemy.pos, newPos), -1);

                                        enemy.pos = newPos;
                                        Dungeon.level.occupyCell(enemy);

                                    }
                                    break;
                                }
                            }
                        }
                        Buff.prolong(enemy, Vertigo.class, Random.NormalIntRange(1, 4));
                    }
                    break;
                case SLAM:
                    BrokenSeal.WarriorShield shield = Buff.affect(target, BrokenSeal.WarriorShield.class);
                    shield.supercharge(dmg / 2);
                    break;
                default:
                    //nothing
                    break;
            }

            if (target.buff(FireImbue.class) != null)
                target.buff(FireImbue.class).proc(enemy);
            if (target.buff(EarthImbue.class) != null)
                target.buff(EarthImbue.class).proc(enemy);
            if (target.buff(FrostImbue.class) != null)
                target.buff(FrostImbue.class).proc(enemy);

            Sample.INSTANCE.play(Assets.SND_HIT, 1, 1, Random.Float(0.8f, 1.25f));
            enemy.sprite.bloodBurstA(target.sprite.center(), dmg);
            enemy.sprite.flash();

            if (!enemy.isAlive()) {
                GLog.i(Messages.capitalize(Messages.get(Char.class, "defeat", enemy.name)));
            }

            Hero hero = (Hero) target;

            //Post-attack behaviour
            switch (type) {
                case CLEAVE:
                    if (!enemy.isAlive()) {
                        //combo isn't reset, but rather increments with a cleave kill, and grants more time.
                        hit(enemy);
                        comboTime = 12f;
                    } else {
                        detach();
                        ActionIndicator.clearAction(Combo.this);
                    }
                    hero.spendAndNext(hero.attackDelay());
                    break;

                case FURY:
                    count--;
                    //fury attacks as many times as you have combo count
                    if (count > 0 && enemy.isAlive()) {
                        target.sprite.attack(enemy.pos, () -> doAttack(enemy));
                    } else {
                        detach();
                        ActionIndicator.clearAction(Combo.this);
                        hero.spendAndNext(hero.attackDelay());
                    }
                    break;

                default:
                    detach();
                    ActionIndicator.clearAction(Combo.this);
                    hero.spendAndNext(hero.attackDelay());
                    break;
            }

        }

        @Override
        public String prompt() {
            if (count >= 10) return Messages.get(Combo.class, "fury_prompt");
            else if (count >= 8) return Messages.get(Combo.class, "crush_prompt");
            else if (count >= 6) return Messages.get(Combo.class, "slam_prompt");
            else if (count >= 4) return Messages.get(Combo.class, "cleave_prompt");
            else return Messages.get(Combo.class, "clobber_prompt");
        }
    };
}
